#include "clib_container_single_linked_list.h"


struct clib_container_single_linked_list_node {
    void *data;
    struct clib_container_single_linked_list_node *next;
};

struct clib_container_single_linked_list {
    size_t size;
    struct clib_container_single_linked_list_node *head;
    struct clib_container_single_linked_list_node *tail;

    void *(*mem_malloc_func)(size_t size);

    void (*mem_free_func)(void *block);

    void *(*mem_copy_func)(void *target, const void *src, size_t size);
};


int clib_container_single_linked_list_create_with_conf(struct clib_container_single_linked_list_conf *conf,
                                                       struct clib_container_single_linked_list **out) {
    clib_check_if_true_return_value(conf == NULL, CLIB_RES_PARAM_ERROR);
    clib_check_if_true_return_value(conf->mem_copy_func == NULL, CLIB_RES_PARAM_ERROR);
    clib_check_if_true_return_value(conf->mem_malloc_func == NULL, CLIB_RES_PARAM_ERROR);
    clib_check_if_true_return_value(conf->mem_free_func == NULL, CLIB_RES_PARAM_ERROR);

    struct clib_container_single_linked_list *list = conf->mem_malloc_func(
            sizeof(struct clib_container_single_linked_list));
    if (!list) {
        return CLIB_RES_MEMORY_ERROR;
    }
    list->mem_malloc_func = conf->mem_malloc_func;
    list->mem_free_func = conf->mem_free_func;
    list->mem_copy_func = conf->mem_copy_func;
    *out = list;
    return CLIB_RES_OK;
}

static int free_all(struct clib_container_single_linked_list *list, void **out) {
    if (list->size == 0) {
        return false;
    }
    int remove_index = 0;
    struct clib_container_single_linked_list_node *n = list->head;
    while (n) {
        if (out != NULL) {
            out[remove_index] = n;
        }
        remove_index += 1;
        struct clib_container_single_linked_list_node *tmp = n->next;
        list->mem_free_func(n);
        n = tmp;
        list->size--;
    }
    return remove_index;
}

int clib_container_single_linked_list_remove_all(struct clib_container_single_linked_list *list, void **out) {
    int free_size = free_all(list, out);
    list->head = NULL;
    list->tail = NULL;
    return free_size;
}


void clib_container_single_linked_list_destroy(struct clib_container_single_linked_list *list) {
    clib_container_single_linked_list_remove_all(list, NULL);
    list->mem_free_func(list);
}


int clib_container_single_linked_list_add_last(struct clib_container_single_linked_list *list, void *element) {
    struct clib_container_single_linked_list_node *node = list->mem_malloc_func(
            sizeof(struct clib_container_single_linked_list_node));
    if (!node) {
        return CLIB_RES_MEMORY_ERROR;
    }
    node->data = element;
    if (list->size == 0) {
        list->head = node;
        list->tail = node;
    } else {
        list->tail->next = node;
        list->tail = node;
    }
    list->size++;
    return CLIB_RES_OK;
}


int clib_container_single_linked_list_add_first(struct clib_container_single_linked_list *list, void *element) {
    struct clib_container_single_linked_list_node *node = list->mem_malloc_func(
            sizeof(struct clib_container_single_linked_list_node));

    if (!node) {
        return CLIB_RES_MEMORY_ERROR;
    }
    node->data = element;
    if (list->size == 0) {
        list->head = node;
        list->tail = node;
    } else {
        node->next = list->head;
        list->head = node;
    }
    list->size++;
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_add(struct clib_container_single_linked_list *list, void *element) {
    return clib_container_single_linked_list_add_last(list, element);
}

static int
get_node_index(struct clib_container_single_linked_list *list, size_t index,
               struct clib_container_single_linked_list_node **node,
               struct clib_container_single_linked_list_node **prev) {
    if (index >= list->size) {
        return CLIB_RES_OUT_OF_BOUNDS_ERROR;
    }
    *node = list->head;
    *prev = NULL;
    size_t i;
    for (i = 0; i < index; i++) {
        *prev = *node;
        *node = (*node)->next;
    }
    return CLIB_RES_OK;
}


int clib_container_single_linked_list_add_with_index(struct clib_container_single_linked_list *list, void *element,
                                                     size_t index) {
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;

    int code = get_node_index(list, index, &node, &prev);

    if (code != CLIB_RES_OK) {
        return code;
    }
    struct clib_container_single_linked_list_node *new = list->mem_malloc_func(
            sizeof(struct clib_container_single_linked_list_node));
    if (!new) {
        return CLIB_RES_MEMORY_ERROR;
    }
    new->data = element;
    if (!prev) {
        new->next = list->head;
        list->head = new;
    } else {
        struct clib_container_single_linked_list_node *tmp = prev->next;
        prev->next = new;
        new->next = tmp;
    }

    list->size++;
    return CLIB_RES_OK;
}


static bool link_all_externally(struct clib_container_single_linked_list *list,
                                struct clib_container_single_linked_list_node **h,
                                struct clib_container_single_linked_list_node **t) {
    struct clib_container_single_linked_list_node *ins = list->head;

    size_t i;
    for (i = 0; i < list->size; i++) {
        struct clib_container_single_linked_list_node *new = list->mem_malloc_func(
                sizeof(struct clib_container_single_linked_list_node));
        if (!new) {
            while (*h) {
                struct clib_container_single_linked_list_node *tmp = (*h)->next;
                list->mem_free_func(*h);
                *h = tmp;
            }
            return false;
        }
        new->data = ins->data;
        if (!*h) {
            *h = new;
            *t = new;
        } else {
            (*t)->next = new;
            *t = new;
        }
        ins = ins->next;
    }
    return true;
}

int clib_container_single_linked_list_add_all(struct clib_container_single_linked_list *list1,
                                              struct clib_container_single_linked_list *list2) {
    if (list2->size == 0) {
        return CLIB_RES_OK;
    }

    struct clib_container_single_linked_list_node *head = NULL;
    struct clib_container_single_linked_list_node *tail = NULL;

    if (link_all_externally(list2, &head, &tail) != CLIB_RES_OK) {
        return CLIB_RES_MEMORY_ERROR;
    }
    if (list1->size == 0) {
        list1->head = head;
        list1->tail = tail;
    } else {
        list1->tail->next = head;
        list1->tail = tail;
    }
    list1->size += list2->size;
    return CLIB_RES_OK;
}


int
clib_container_single_linked_list_add_all_with_index(struct clib_container_single_linked_list *list1,
                                                     struct clib_container_single_linked_list *list2,
                                                     size_t index) {
    if (list2->size == 0) {
        return CLIB_RES_OK;
    }
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;

    int code = get_node_index(list1, index, &node, &prev);

    if (code != CLIB_RES_OK) {
        return code;
    }

    struct clib_container_single_linked_list_node *head = NULL;
    struct clib_container_single_linked_list_node *tail = NULL;

    if (link_all_externally(list2, &head, &tail) != CLIB_RES_OK) {
        return CLIB_RES_MEMORY_ERROR;
    }
    if (!prev) {
        tail->next = node;
        list1->head = head;
    } else {
        prev->next = head;
        tail->next = node;
    }
    list1->size += list2->size;
    return CLIB_RES_OK;
}

static int
get_node(struct clib_container_single_linked_list *list, void *element,
         struct clib_container_single_linked_list_node **node, struct clib_container_single_linked_list_node **prev) {
    *node = list->head;
    *prev = NULL;
    while (*node) {
        if ((*node)->data == element) {
            return CLIB_RES_OK;
        }
        *prev = *node;
        *node = (*node)->next;
    }
    return CLIB_RES_OUT_OF_BOUNDS_ERROR;
}

static void *
unlink_node(struct clib_container_single_linked_list *list, struct clib_container_single_linked_list_node *node,
            struct clib_container_single_linked_list_node *prev) {
    void *data = node->data;

    if (prev) {
        prev->next = node->next;
    } else {
        list->head = node->next;
    }

    if (!node->next) {
        list->tail = prev;
    }
    list->mem_free_func(node);
    list->size--;

    return data;
}

int
clib_container_single_linked_list_remove(struct clib_container_single_linked_list *list, void *element, void **out) {
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;
    int code = get_node(list, element, &node, &prev);
    if (code != CLIB_RES_OK) {
        return code;
    }
    void *val = unlink_node(list, node, prev);
    if (out) {
        *out = val;
    }
    return CLIB_RES_OK;
}


int clib_container_single_linked_list_remove_with_index(struct clib_container_single_linked_list *list, size_t index,
                                                        void **out) {
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;
    int code = get_node_index(list, index, &node, &prev);
    if (code != CLIB_RES_OK) {
        return code;
    }
    void *e = unlink_node(list, node, prev);
    if (out) {
        *out = e;
    }
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_remove_first(struct clib_container_single_linked_list *list, void **out) {
    if (list->size == 0) {
        return CLIB_RES_OUT_OF_BOUNDS_ERROR;
    }
    void *e = unlink_node(list, list->head, NULL);
    if (out) {
        *out = e;
    }
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_remove_last(struct clib_container_single_linked_list *list, void **out) {
    if (list->size == 0) {
        return CLIB_RES_OUT_OF_BOUNDS_ERROR;
    }

    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;

    int code = get_node_index(list, list->size - 1, &node, &prev);

    if (code != CLIB_RES_OK)
        return code;
    void *e = unlink_node(list, node, prev);
    if (out) {
        *out = e;
    }
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_replace_with_index(struct clib_container_single_linked_list *list, void *element,
                                                         size_t index, void **out) {
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;

    int code = get_node_index(list, index, &node, &prev);

    if (code != CLIB_RES_OK) {
        return code;
    }
    void *old = node->data;
    node->data = element;
    if (out) {
        *out = old;
    }
    return CLIB_RES_OK;
}


int clib_container_single_linked_list_get_first(struct clib_container_single_linked_list *list, void **out) {
    if (list->size == 0) {
        return CLIB_RES_OUT_OF_BOUNDS_ERROR;
    }
    *out = list->head->data;
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_get_last(struct clib_container_single_linked_list *list, void **out) {
    if (list->size == 0) {
        return CLIB_RES_OUT_OF_BOUNDS_ERROR;
    }
    *out = list->tail->data;
    return CLIB_RES_OK;
}

int clib_container_single_linked_list_get_with_index(struct clib_container_single_linked_list *list, size_t index,
                                                     void **out) {
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *node = NULL;

    int code = get_node_index(list, index, &node, &prev);

    if (code != CLIB_RES_OK) {
        return code;
    }
    *out = node->data;
    return CLIB_RES_OK;
}

size_t clib_container_single_linked_list_get_current_size(struct clib_container_single_linked_list *list) {
    return list->size;
}

void clib_container_single_linked_list_reserve(struct clib_container_single_linked_list *list) {
    if (list->size == 0 || list->size == 1) {
        return;
    }
    struct clib_container_single_linked_list_node *prev = NULL;
    struct clib_container_single_linked_list_node *next = NULL;
    struct clib_container_single_linked_list_node *flip = list->head;
    list->tail = list->head;
    while (flip) {
        next = flip->next;
        flip->next = prev;
        prev = flip;
        flip = next;
    }
    list->head = prev;
}


size_t clib_container_single_linked_list_contains(struct clib_container_single_linked_list *list, void *element) {
    struct clib_container_single_linked_list_node *node = list->head;
    size_t e_count = 0;
    while (node) {
        if (node->data == element) {
            e_count++;
        }
        node = node->next;
    }
    return e_count;
}


int clib_container_single_linked_list_index_of(struct clib_container_single_linked_list *list, void *element,
                                               size_t *index) {
    struct clib_container_single_linked_list_node *node = list->head;
    size_t i = 0;
    while (node) {
        if (node->data == element) {
            *index = i;
            return CLIB_RES_OK;
        }
        i++;
        node = node->next;
    }
    return CLIB_RES_OUT_OF_BOUNDS_ERROR;
}